<?php


namespace EchoPHP\Foundation;

use EchoPHP\Log\LogServiceProvider;
use EchoPHP\Route\RouterServiceProvider;
use EchoPHP\View\ViewServiceProvider;

class Application extends Container {

    /**
     * 核心框架源码版本号
     * @var string
     */
    protected $version = '0.1.1';

    /**
     * 项目根目录
     * @var string
     */
    protected $basePath;

    /**
     * 应用注册的一般服务提供者
     * @var array
     */
    protected $serviceProviders = [];

    /**
     * 应用注册的延迟加载服务提供器
     * @var array
     */
    protected $deferredServices = [];

    /**
     * 应用注册的服务提供者名称
     * @var array
     */
    protected $loadedProviders = [];

    /**
     * 标记应用程序是否已经启动
     * @var bool
     */
    protected $booted = false;

    /**
     * 标记应用程序是否已经执行初始化
     * @var bool
     */
    protected $hasBeenBootstrapped = false;

    /**
     * 应用程序启动时执行的回调
     * @var array
     */
    protected $bootingCallbacks = [];

    /**
     * 应用程序启动后执行回调
     * @var array
     */
    protected $bootedCallbacks = [];

    /**
     * 项目环境变量所在目录
     * @var
     */
    protected $environmentPath;

    /**
     * 项目环境变量文件名
     * @var string
     */
    protected $environmentFile = '.env';

    /**
     * 创建应用程序实例
     * @param $basePath
     */
    public function __construct($basePath) {
        //绑定项目基础目录
        $this->setBasePath($basePath);
        $this->registerBaseBindings();
        $this->registerBaseServiceProvider();
        $this->registerCoreContainerAliases();

        $this->initGlobalHeaders();
    }

    /**
     * 构建容器对象实例，如果实例对象注册为延时服务提供器，那么预先实例化这个延时服务提供器
     * @param $abstract
     * @param array $parameters
     * @return mixed|object
     * @throws \Exception
     */
    public function make($abstract, array $parameters = []) {
        $abstract = $this->getAlias($abstract);
        if (isset($this->deferredServices[$abstract]) && !isset($this->instances[$abstract])) {
            $this->loadDeferredProvider($abstract);
        }

        return parent::make($abstract, $parameters);
    }

    /**
     * 绑定项目基础路径别名即物理路径地址
     * @param $basePath
     */
    protected function setBasePath($basePath) {
        $this->basePath = rtrim($basePath, '\/');

        $this->instance('path', $this->basePath . '/app');
        $this->instance('path.base', $this->basePath);
        $this->instance('path.framework', $this->basePath . '/framework/src/EchoPHP');
        $this->instance('path.config', $this->basePath . '/config');
        $this->instance('path.routes', $this->basePath . '/routes');
        $this->instance('path.storage', $this->basePath . '/storage');
        $this->instance('path.log', $this->basePath . '/storage/log');
        $this->instance('path.bootstrap', $this->basePath . '/bootstrap');
        $this->instance('path.view', $this->basePath . '/views');
    }

    /**
     * 初始化全局变量头部
     */
    protected function initGlobalHeaders() {
        $this->instance('global_headers', []);
    }

    /**
     * 添加全局响应头
     * @param $name
     * @param $value
     * @throws \Exception
     */
    public function addGlobalHeaders($name, $value) {
        $globalHeaders = $this->make('global_headers');
        $globalHeaders[$name] = $value;
        $this->instance('global_headers', $globalHeaders);
    }

    /**
     * 获取全局响应头
     * @return mixed|object
     * @throws \Exception
     */
    public function getGlobalHeaders() {
        return $this->make('global_headers');
    }

    /**
     * 注册基本实例绑定，将当前类即application注册类项目容器类
     */
    protected function registerBaseBindings() {
        static::setInstance($this);
        $this->instance('app', $this);
        $this->instance(Container::class, $this);
    }

    /**
     * 注册基础服务提供器，包括 路由、日志处理
     */
    protected function registerBaseServiceProvider() {
        $this->register(new LogServiceProvider($this));
        $this->register(new RouterServiceProvider($this));
    }

    /**
     * 注册核心容器类对象别名
     */
    protected function registerCoreContainerAliases() {
        //核心类库别名，定义格式，别名 => 类库数组集合  即多个类库指向同一个别名
        $aliases = [
            'app' => [\EchoPHP\Foundation\Application::class, \EchoPHP\Foundation\Container::class],
            'request' => [\EchoPHP\Http\Request::class],
            'route' => [\EchoPHP\Route\Router::class],
            'config' => [\EchoPHP\Config\Config::class],
            'db' => [\EchoPHP\Database\DatabaseManager::class],
            'db.factory' => [\EchoPHP\Database\ConnectionFactory::class],
            'db.connection' => [\EchoPHP\Database\Connections\Connection::class, \EchoPHP\Database\Connections\Connection::class],
        ];

        foreach ($aliases as $abstract => $aliasItem) {
            foreach ($aliasItem as $alias) {
                $this->setAlias($abstract, $alias);
            }
        }
    }

    /**
     * 注册服务提供者
     */
    public function registerConfiguredProviders() {
        $providers = config('app.providers', []);

        foreach ($providers as $provider) {
            $instance = new $provider($this);
            if ($instance->isDeferred()) {
                //如果是延时服务提供器，则先标记
                foreach ($instance->provides() as $service) {
                    $this->deferredServices[$service] = $provider;
                }
            } else {
                $this->register($instance);
            }
        }
    }

    /**
     * 注册服务器提供器
     * @param $provider
     * @param bool $force
     * @return mixed|null
     */
    public function register($provider, $force = false) {
        $registered = $this->getServiceProvider($provider);
        if ($registered && !$force) {
            return $registered;
        }
        //如果传入的是服务提供者的名字，则先实例化
        if (is_string($provider)) {
            $provider = new $provider($this);
        }
        //执行服务提供者的注册
        $provider->register();

        //如果存在bindings属性，则执行服务的普通绑定
        if (property_exists($provider, 'bindings')) {
            foreach ($provider->bindings as $key => $value) {
                $this->bind($key, $value);
            }
        }

        //如果存在singletons 属性，则执行服务的单例绑定
        if (property_exists($provider, 'singletons')) {
            foreach ($provider->singletons as $key => $value) {
                $this->singleton($key, $value);
            }
        }

        $this->markAsRegistered($provider);

        if ($this->booted) {
            $this->bootProvider($provider);
        }

        return $provider;
    }

    /**
     * 校验服务提供者是否被注册
     * @param string|object $provider 服务提供器名称或者实例
     * @return bool
     */
    public function chkRegistered($provider) {
        $provider = is_string($provider) ? $provider : get_class($provider);
        return isset($this->loadedProviders[$provider]) ? true : false;
    }

    /**
     * 获取服务提供器
     * @param $provider     服务提供者名称
     * @return mixed|null   服务提供者实例或者null
     */
    public function getServiceProvider($provider) {
        $provider = is_string($provider) ? $provider : get_class($provider);
        foreach ($this->serviceProviders as $serviceProvider) {
            if ($serviceProvider instanceof $provider) {
                return $serviceProvider;
            }
        }
        return null;
    }

    /**
     * 获取所有已经注册的服务提供者
     * @return array
     */
    public function getServiceProviders() {
        return $this->serviceProviders;
    }

    /**
     * 标记服务提供者已注册到应用程序中
     * @param $provider
     */
    protected function markAsRegistered($provider) {
        $this->serviceProviders[] = $provider;
        $this->loadedProviders[get_class($provider)] = true;
    }

    /**
     * 添加延迟服务提供器
     * @param $servicesProviders
     */
    public function addDeferredServiceProviders($servicesProviders) {
        $this->deferredServices = array_merge($this->deferredServices, $servicesProviders);
    }

    /**
     * 加载全部延时服务提供者
     */
    public function loadDeferredProviders() {
        foreach ($this->deferredServices as $service => $provider) {
            $this->loadDeferredProvider($service);
        }

        $this->deferredServices = [];
    }

    /**
     * 加载延时服务提供器
     * @param $service
     */
    public function loadDeferredProvider($service) {
        if (!isset($this->deferredServices[$service])) {
            return;
        }
        $provider = $this->deferredServices[$service];
        if (isset($this->loadedProviders[$provider])) {
            return;
        }
        unset($this->deferredServices[$service]);
        //注册服务提供器
        $this->register($instance = new $provider($this));
        if (!$this->booted) {
            //如果延时服务提供器已经加载，但应用还未正式启动，此时需要将延时服务提供器追加到服务提供器中
            $this->booting(function () use ($instance) {
                $this->bootProvider($instance);
            });
        }
    }

    /**
     * 判断应用程序是否已经执行初始化程序
     * @return bool
     */
    public function hasBeenBootstrapped() {
        return $this->hasBeenBootstrapped;
    }

    /**
     * 使用给定的启动类执行初始化程序
     * @param array $bootstrappers
     * @throws \Exception
     */
    public function bootstrapWith(array $bootstrappers) {
        $this->hasBeenBootstrapped = true;

        foreach ($bootstrappers as $bootstrapper) {
            $this->make($bootstrapper)->bootstrap($this);
        }
    }

    /**
     * 启动项目的服务提供者
     */
    public function boot() {
        if ($this->booted) {
            return;
        }
        $this->handleAppCallbacks($this->bootingCallbacks);
        array_walk($this->serviceProviders, function ($p) {
            $this->bootProvider($p);
        });
        $this->booted = true;
        $this->handleAppCallbacks($this->bootedCallbacks);
    }

    /**
     * 注册启动前监听器
     * @param $callback
     */
    public function booting($callback) {
        $this->bootingCallbacks[] = $callback;
    }

    /**
     * 注册启动后监听器
     * @param $callback
     */
    public function booted($callback) {
        $this->bootedCallbacks[] = $callback;
        if ($this->booted) {
            $this->handleAppCallbacks([$callback]);
        }
    }

    /**
     * 执行应用程序回调函数
     * @param $callbacks
     */
    public function handleAppCallbacks($callbacks) {
        foreach ($callbacks as $callback) {
            call_user_func($callback, $this);
        }
    }

    /**
     * 启动服务提供者
     * @param ServiceProvider $provider
     * @return mixed
     */
    protected function bootProvider(ServiceProvider $provider) {
        if (method_exists($provider, 'boot')) {
            return $provider->boot();
        }
    }

    /**
     * 获取版本号
     * @return string
     */
    public function getVersion() {
        return $this->version;
    }

    /**
     * 获取项目环境变量目录
     * @return string
     */
    public function environmentPath() {
        return $this->environmentPath ?: $this->basePath;
    }

    /**
     * 设置项目环境变量目录
     * @param $path
     * @return $this
     */
    public function setEnvironmentPath($path) {
        $this->environmentPath = $path;
        return $this;
    }

    /**
     * 获取项目环境变量文件名
     * @return string
     */
    public function environmentFile() {
        return $this->environmentFile ?: '.env';
    }

    /**
     * 设置项目环境变量名
     * @param $file
     * @return $this
     */
    public function serEnvironmentFile($file) {
        $this->environmentFile = $file;
        return $this;
    }

    /**
     * 判断是否控制台运行
     * cli模式或者 phpdbg（php调试器）
     * @return bool
     */
    public function runningInConsole() {
        return php_sapi_name() === 'cli' || php_sapi_name() === 'phpdbg';
    }
}